/*
 * Copyright (C) 2011 Pierre-Michel Villa (https://plus.google.com/104431153892799928626/about)
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.climbatize.imagemanager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

/**
 * This helper class download images from the Internet and binds those with the provided ImageView.
 * A local cache of downloaded images is maintained internally to improve performance and on sdcard.
 * 
 * <p>
 * It requires the INTERNET permission, which should be added to your application's manifest file.
 * </p>
 * 
 * 
 * 
 * Inspired by {@link http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html}
 * 
 * 
 * @version 4.0
 * @author Pierre-Michel Villa
 * @author Alex Kirchen
 * @author Gilles Debunne @ google
 * 
 * @since 2011/10/06
 */
public class ImageManager {
    private static ImageView                         sampleImageView           = null;
    //private static final String                      LOG_TAG                   = "ImageManager";
    private static final String                      state                     = Environment.getExternalStorageState();
    private static ImageManager                      instance;
    private String                                   mfolder;
    private String                                   mSubfolder;
    private boolean                                  mExternalStorageAvailable = false;
    private boolean                                  mExternalStorageWriteable = false;
    private HashMap<ImageView, ImageManagerListener> mListeners                = null;
    private LinkedHashMap<ImageView, String>         mDownloadList;
    private Context                                  base_activity             = null;
    SimpleDateFormat                                 mDateFormater             = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.UK);
    /**
     * Variable used to define if drawables will be returned scaled or not
     * 
     * @see {@link ImageManager#getInstance(Activity, ImageManagerListener)}
     */
    public boolean                                   displayWithMetrics        = true;

    private Drawable                                 default_img               = null;

    private boolean                                  keepBackground            = true;
    private boolean                                  isDownloading;
    private HashMap<String, ImageView>               mImageViewByUrl;

    /**
     * Preferer use of {@link #getInstance(Activity, ImageManagerListener)}
     */
    public ImageManager() {
        this.mListeners = new HashMap<ImageView, ImageManagerListener>();
        this.mDownloadList = new LinkedHashMap<ImageView, String>();
        this.mImageViewByUrl = new HashMap<String, ImageView>();
    }

    /**
     * get a thread safe singleton of {@link ImageManager}
     * 
     * @return
     */
    public static synchronized ImageManager getInstance() {
        if (null == instance) {
            instance = new ImageManager();
        }
        return instance;
    }

    /**
     * get a thread safe singleton of {@link ImageManager} with a base activity to calibrate image density
     * 
     * @return
     */
    public static synchronized ImageManager getInstance(Context a) {
        ImageManager id = getInstance();
        id.base_activity = a;
        return id;
    }

    /**
     * 
     * @param listener
     */
    public void addListener(ImageView imageView, ImageManagerListener listener) {
        if (null != listener) {
            this.mListeners.put(imageView, listener);
        }

    }

    /**
     * 
     * @param listened
     *            The imageView that is listened
     * @return true if the listener were listening to this instance and has been removed
     */
    public ImageManagerListener removeListener(ImageView listened) {

        return this.mListeners.remove(listened);

    }

    /**
     * 
     * @param url
     * @param imageView
     * @throws NotInCacheException
     */
    public void addToDownloadList(String url, ImageView imageView, ImageManagerListener listener) throws NotInCacheException {
        mDownloadList.put(imageView, url);
        mImageViewByUrl.put(url, imageView);
        addListener(imageView, listener);

        if (sampleImageView == null) sampleImageView = imageView;

        if (!isDownloading) {

            download(url, imageView, listener, null);
        }
    }

    /**
     * Download the specified image from the Internet and binds it to the provided ImageView. The
     * binding is immediate if the image is found in the cache and will be done asynchronously
     * otherwise. A null bitmap will be associated to the ImageView if an error occurs.
     * 
     * @param url
     *            The URL of the image to download.
     * @param imageView
     *            The ImageView to bind the downloaded image to.
     * @throws NotInCacheException
     * 
     * @see download(String url, ImageView imageView, String subfolder)
     * 
     */
    public void download(String url, ImageView imageView) throws NotInCacheException {
        download(url, imageView, null, null);
    }

    /**
     * Download the specified image from the Internet and binds it to the provided ImageView. The
     * binding is immediate if the image is found in the cache and will be done asynchronously
     * otherwise. A null bitmap will be associated to the ImageView if an error occurs.
     * 
     * @param url
     * @param imageView
     * @param subfolder
     *            the subfolder in sdcard cache
     * @param listener
     * @throws NotInCacheException
     * 
     */
    public void download(String url, ImageView imageView, ImageManagerListener _listener, String subfolder) throws NotInCacheException {
        if (subfolder != null) {
            mSubfolder = "/" + subfolder;
        } else {
            mSubfolder = "";
        }

        addListener(imageView, _listener);

        mfolder = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + imageView.getContext().getPackageName() + "/files/images"
                  + mSubfolder;

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            // We can read and write the media
            mExternalStorageAvailable = mExternalStorageWriteable = true;

            File nomedia = new File(mfolder + "/.nomedia");
            if (!nomedia.exists()) {
                try {
                    (new File(mfolder)).mkdirs();
                    nomedia.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            // We can only read the media
            mExternalStorageAvailable = true;
            mExternalStorageWriteable = false;
        } else {
            // Something else is wrong. It may be one of many other states, but
            // all we need
            // to know is we can neither read nor write
            mExternalStorageAvailable = mExternalStorageWriteable = false;
        }

        resetPurgeTimer();
        Bitmap bitmap = getBitmapFromCache(url);

        if (url.startsWith("http")) {
            forceDownload(url, imageView, _listener);
        } else {
            throw new NotInCacheException("image " + url + "is not in cache");
        }

        if (bitmap != null) {

            // cancelPotentialDownload(url, imageView);
            imageView.setImageDrawable(getScaledDrawable(bitmap, imageView));
            if (!keepBackground) imageView.setBackgroundDrawable(null);

            ImageManagerListener listener = mListeners.get(imageView);
            if (listener != null) {
                listener.onImageDownloaded(imageView, url, mfolder + "/" + URLEncoder.encode(url), imageView.getDrawable().getIntrinsicWidth(),
                                           imageView.getDrawable().getIntrinsicWidth());
            }

            mListeners.remove(imageView);
            // if (updateCache) {
            // if (mDownloadList.size() != 0) {
            // final ImageView dlImageView = (ImageView) mDownloadList.keySet().toArray()[0];
            // download(mDownloadList.get(dlImageView), dlImageView);
            // }
            // } else
            mDownloadList.remove(imageView);
            mImageViewByUrl.remove(url);

        }
    }

    /**
     * 
     * Stores a bitmap to the application cache
     * 
     * @param name
     *            the name of the bitmap to store
     * @param bmp
     *            the bitmap itself
     */
    public void store(String name, Bitmap bmp, String date) {
        addBitmapToCache(name, bmp, date);
    }

    /**
     * Same as download but the image is always downloaded and the cache is not used.
     * Kept private at the moment as its interest is not clear.
     * 
     * @throws NotInCacheException
     */
    public void forceDownload(String url, ImageView imageView, ImageManagerListener _listener) throws NotInCacheException {
        // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.

        if (_listener != null) {
            mListeners.put(imageView, _listener);
        }

        if (url == null) {
            imageView.setImageDrawable(null);
            throw new NotInCacheException("url can't be null");
        }

        //Log.d(LOG_TAG, "forceDownload : ?");
        if (cancelPotentialDownload(url, imageView)) {
            //Log.d(LOG_TAG, "forceDownload : ok");
            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
            imageView.setBackgroundDrawable((default_img));
            DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
            imageView.setImageDrawable(downloadedDrawable);
            imageView.setMinimumHeight(156);
            try {
                //Log.e(LOG_TAG, "download " + url);
                task.execute(url);
            } catch (RejectedExecutionException e) {

                cancelPotentialDownload(url, imageView);
                //Log.e(LOG_TAG, "RejectedExecutionException (Too many downloads?)", e);
                throw new NotInCacheException("RejectedExecutionException (Too many downloads?)");
            }

        }
    }

    /**
     * Returns true if the current download has been canceled or if there was no download in
     * progress on this image view.
     * Returns false if the download in progress deals with the same url. The download is not
     * stopped in that case.
     */
    private static boolean cancelPotentialDownload(String url, ImageView imageView) {
        BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
        if (bitmapDownloaderTask != null) {
            String bitmapUrl = bitmapDownloaderTask.url;
            if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
                bitmapDownloaderTask.cancel(true);
            } else {
                // The same URL is already being downloaded.
                return false;
            }
        }
        return true;
    }

    /**
     * @param imageView
     *            Any imageView
     * @return Retrieve the currently active download task (if any) associated with this imageView.
     *         null if there is no such task.
     */
    private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
        if (imageView != null) {
            Drawable drawable = imageView.getDrawable();
            if (drawable instanceof DownloadedDrawable) {
                DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
                return downloadedDrawable.getBitmapDownloaderTask();
            }
        }
        return null;
    }

    public Drawable getDefault_img() {
        return default_img;
    }

    public void setDefault_img(Bitmap bitmap) {
        this.default_img = new BitmapDrawable(bitmap);
    }

    public void setDefault_img(Drawable drawable) {
        this.default_img = drawable;
    }

    Bitmap downloadBitmap(String url) {

        // AndroidHttpClient is not allowed to be used from the main thread

        final HttpClient client = AndroidHttpClient.newInstance("Android");
        final HttpGet getRequest = new HttpGet(url);

        try {
            File f = new File(mfolder, URLEncoder.encode(url));

            if (f.exists() && (((new Date()).getTime() - f.lastModified()) < 5 * 1000 * 60)) return BitmapFactory.decodeFile(f.getPath());

            URL connexion = new URL(url);
            URLConnection urlConnection;
            urlConnection = connexion.openConnection();
            urlConnection.connect();
            //int file_size = urlConnection.getContentLength();
            String date = urlConnection.getHeaderField("Last-Modified");
            //Log.d(LOG_TAG, url + " : " + file_size);

            if (f.exists() && (f.lastModified() == mDateFormater.parse(date).getTime() || !isOnline(sampleImageView))) {
                //Log.i(LOG_TAG, "pic " + url + " up to date");
                Bitmap b = BitmapFactory.decodeFile(f.getPath());

                synchronized (sHardBitmapCache) {
                    sHardBitmapCache.remove(url);
                    sHardBitmapCache.put(url, b);
                }
                return b;
            } else {

                if (f.exists()) f.delete();

                // if(Constants.DEBUGMODE) //Log.i(Constants.PROJECT_TAG, "Downloading " + url);

                InputStream inputStream = null;
                try {
                    //Log.d(LOG_TAG, "loading pic");
                    inputStream = urlConnection.getInputStream();

                    // if(Constants.DEBUGMODE) //Log.d(Constants.PROJECT_TAG, "image url:" + urlString);
                    Drawable drawable = Drawable.createFromStream(inputStream, "src");

                    Bitmap bm = ((BitmapDrawable) drawable).getBitmap();

                    // if(Constants.DEBUGMODE) //Log.d(Constants.PROJECT_TAG, "Bm width : " + bm.getWidth());

                    // if(Constants.DEBUGMODE) //Log.d(Constants.PROJECT_TAG, "got a thumbnail drawable: " + drawable.getBounds() + ", " +
                    // drawable.getIntrinsicHeight() + ","
                    // + drawable.getIntrinsicWidth() + ", " + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
                    addBitmapToCache(url, bm, date);
                    inputStream.close();
                    return bm;

                    // return BitmapFactory.decodeStream(inputStream);
                    // Bug on slow connections, fixed in future release.
                    // return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    urlConnection = null;
                }
            }

        } catch (IOException e) {
            Collection<String> urls = mDownloadList.values();
            int i = 0;
            for (String string : urls) {
                if (url.equals(string)) break;
                i++;
            }

            ImageView iv = null;
            int j = 0;
            Iterator<ImageView> set = mDownloadList.keySet().iterator();
            while (set.hasNext()) {
                iv = set.next();
                if (i == j) break;
                j++;
            }
            getRequest.abort();
            if (iv != null) {
                ImageManagerListener listener = mListeners.get(iv);

                if (listener != null) {
                    listener.onImageDownloadFailed(url, mfolder + "/" + URLEncoder.encode(url), "Download canceled : IOException");
                }

                mListeners.remove(iv);
            }

            //Log.e(LOG_TAG, "I/O error while retrieving bitmap from " + url, e);
        } catch (IllegalStateException e) {
            getRequest.abort();
            Collection<String> urls = mDownloadList.values();
            int i = 0;
            for (String string : urls) {
                if (url.equals(string)) break;
                i++;
            }

            ImageView iv = null;
            int j = 0;
            Iterator<ImageView> set = mDownloadList.keySet().iterator();
            while (set.hasNext()) {
                iv = set.next();
                if (i == j) break;
                j++;
            }
            getRequest.abort();
            if (iv != null) {
                ImageManagerListener listener = mListeners.get(iv);
                if (listener != null) {
                    listener.onImageDownloadFailed(url, mfolder + "/" + URLEncoder.encode(url), "Download canceled : IllegalStateException");
                }

                mListeners.remove(iv);
            }
            //Log.e(LOG_TAG, "Incorrect URL: " + url);
        } catch (Exception e) {
            getRequest.abort();

            Collection<String> urls = mDownloadList.values();
            int i = 0;
            for (String string : urls) {
                if (url.equals(string)) break;
                i++;
            }

            ImageView iv = null;
            int j = 0;
            Iterator<ImageView> set = mDownloadList.keySet().iterator();
            while (set.hasNext()) {
                iv = set.next();
                if (i == j) break;
                j++;
            }
            getRequest.abort();
            if (iv != null) {
                ImageManagerListener listener = mListeners.get(iv);
                if (listener != null) {
                    listener.onImageDownloadFailed(url, mfolder + "/" + URLEncoder.encode(url), "Download canceled : Exception");
                }
                //Log.e(LOG_TAG, "Error while retrieving bitmap from " + url, e);

                mListeners.remove(iv);
            }
        } finally {
            if ((client instanceof AndroidHttpClient)) {
                ((AndroidHttpClient) client).close();
            }
        }
        return null;
    }

    /*
     * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
     */
    static class FlushedInputStream extends FilterInputStream {
        public FlushedInputStream(InputStream inputStream) {
            super(inputStream);
        }

        @Override
        public long skip(long n) throws IOException {
            long totalBytesSkipped = 0L;
            while (totalBytesSkipped < n) {
                long bytesSkipped = in.skip(n - totalBytesSkipped);
                if (bytesSkipped == 0L) {
                    int b = read();
                    if (b < 0) {
                        break; // we reached EOF
                    } else {
                        bytesSkipped = 1; // we read one byte
                    }
                }
                totalBytesSkipped += bytesSkipped;
            }
            return totalBytesSkipped;
        }
    }

    /**
     * The actual AsyncTask that will asynchronously download the image.
     */
    class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
        private String                         url;
        private final WeakReference<ImageView> imageViewReference;

        public BitmapDownloaderTask(ImageView imageView) {
            imageViewReference = new WeakReference<ImageView>(imageView);
        }

        @Override
        protected void onPreExecute() {
            isDownloading = true;
            super.onPreExecute();
        }

        /**
         * Actual download method.
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            url = params[0];
            return downloadBitmap(url);
        }

        /**
         * Once the image is downloaded, associates it to the imageView
         */
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
                bitmap = null;
            }

            if (imageViewReference != null) {
                ImageView imageView = imageViewReference.get();
                BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
                // Change bitmap only if this process is still associated with it
                // Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode)
                if ((this == bitmapDownloaderTask)) {

                    Drawable d = getScaledDrawable(bitmap, imageView);
                    imageView.setImageDrawable(d);
                    // imageView.setImageBitmap(bitmap);
                    if (!keepBackground) imageView.setBackgroundDrawable(null);

                    ImageManagerListener listener = mListeners.get(imageView);
                    if (listener != null) {
                        listener.onImageDownloaded(imageView, url, mfolder + "/" + URLEncoder.encode(url), imageView.getDrawable().getIntrinsicWidth(),
                                                   imageView.getDrawable().getIntrinsicHeight());

                    }

                    mListeners.remove(imageView);

                }

                mDownloadList.remove(imageView);
                mImageViewByUrl.remove(url);
            }

            if (mDownloadList.size() != 0) {
                try {
                    download(mDownloadList.get(mDownloadList.keySet().toArray()[0]), (ImageView) mDownloadList.keySet().toArray()[0],
                             mListeners.get((ImageView) mDownloadList.keySet().toArray()[0]), null);
                } catch (NotInCacheException e) {
                    //Log.e(LOG_TAG, "Can't cache image", e);
                }
            } else {
                isDownloading = false;
            }

        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }
    }

    /**
     * A fake Drawable that will be attached to the imageView while the download is in progress.
     * 
     * <p>
     * Contains a reference to the actual download task, so that a download task can be stopped if a new binding is required, and makes sure that only the last
     * started download process can bind its result, independently of the download finish order.
     * </p>
     */
    static class DownloadedDrawable extends ColorDrawable {
        private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

        public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
            super(Color.TRANSPARENT);
            bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
        }

        public BitmapDownloaderTask getBitmapDownloaderTask() {
            return bitmapDownloaderTaskReference.get();
        }
    }

    /*
     * Cache-related fields and methods.
     * We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
     * Garbage Collector.
     */

    private static final int                                              HARD_CACHE_CAPACITY = 10;
    private static final int                                              DELAY_BEFORE_PURGE  = 10 * 1000;                                                                    // in
                                                                                                                                                                               // milliseconds

    // Hard cache, with a fixed maximum capacity and a life duration
    private final HashMap<String, Bitmap>                                 sHardBitmapCache    = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2,
                                                                                                                                  0.75f, true) {

                                                                                                  private static final long serialVersionUID = 244560809668581534L;

                                                                                                  @Override
                                                                                                  protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
                                                                                                      if (size() > HARD_CACHE_CAPACITY) {
                                                                                                          // Entries push-out of hard reference cache are
                                                                                                          // transferred to soft reference cache
                                                                                                          sSoftBitmapCache.put(eldest.getKey(),
                                                                                                                               new SoftReference<Bitmap>(
                                                                                                                                                         eldest.getValue()));
                                                                                                          return true;
                                                                                                      } else {
                                                                                                          return false;
                                                                                                      }
                                                                                                  }
                                                                                              };

    // Soft cache for bitmaps kicked out of hard cache
    private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache    = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
                                                                                                                                                     HARD_CACHE_CAPACITY / 2);

    private final Handler                                                 purgeHandler        = new Handler();

    private final Runnable                                                purger              = new Runnable() {
                                                                                                  public void run() {
                                                                                                      clearCache();
                                                                                                  }

                                                                                              };

    /**
     * Adds this bitmap to the cache.
     * 
     * @param bitmap
     *            The newly downloaded bitmap.
     */
    private void addBitmapToCache(String url, Bitmap bitmap, String date) {
        if (bitmap != null) {

            if (mExternalStorageWriteable) {
                File f = new File(mfolder, URLEncoder.encode(url));
                // if(Constants.DEBUGMODE) //Log.d(Constants.PROJECT_TAG, f.getPath());
                try {
                    if (f != null && f.createNewFile()) {

                        OutputStream out = new FileOutputStream(f);

                        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
                        out.flush();
                        out.close();
                        // byte buf[]=new byte[1024];
                        // int len;
                        // while((len=is.read(buf))>0)
                        // out.write(buf,0,len);
                        out.close();

                        //

                        f.setLastModified((new Date()).getTime());

                    }
                } catch (FileNotFoundException e) {
                    //Log.e(LOG_TAG, "FileNotFoundException in addBitmapToCache", e);
                } catch (IOException e) {
                    //Log.e(LOG_TAG, "IOException in addBitmapToCache", e);
                }
            }

            synchronized (sHardBitmapCache) {
                sHardBitmapCache.put(url, bitmap);
            }

        }
    }

    /**
     * @param url
     *            The URL of the image that will be retrieved from the cache.
     * @return The cached bitmap or null if it was not found.
     */
    private Bitmap getBitmapFromCache(String url) {

        if (mExternalStorageAvailable && url != null) {
            File f = new File(mfolder, URLEncoder.encode(url));

            if (f.exists()) {
                try {
                    return BitmapFactory.decodeFile(f.getPath());

                } catch (Exception e) {
                    //Log.e(LOG_TAG, "Error in retrieving picture", e);

                }
            }
        }

        // First try the hard reference cache
        synchronized (sHardBitmapCache) {
            final Bitmap bitmap = sHardBitmapCache.get(url);
            if (bitmap != null) {
                // Bitmap found in hard cache
                // Move element to first position, so that it is removed last
                sHardBitmapCache.remove(url);
                sHardBitmapCache.put(url, bitmap);
                return bitmap;
            }
        }

        // Then try the soft reference cache
        try {
            SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
            if (bitmapReference != null) {
                final Bitmap bitmap = bitmapReference.get();
                if (bitmap != null) {
                    // Bitmap found in soft cache
                    return bitmap;
                } else {
                    // Soft reference has been Garbage Collected
                    sSoftBitmapCache.remove(url);
                }
            }
        } catch (Exception e) {
            return null;
        }

        return null;
    }

    /**
     * Clears the image cache used internally to improve performance. Note that for memory
     * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
     */
    public void clearCache() {
        //Log.i(LOG_TAG, "clearCache");
        sHardBitmapCache.clear();
        sSoftBitmapCache.clear();
    }

    public void clearView(ViewGroup v) {
        if (v == null) return;
        int children = v.getChildCount();
        for (int i = 0; i < children; i++) {
            View toClear = v.getChildAt(i);
            if (toClear instanceof ImageView) {
                if (((ImageView) toClear).getDrawable() instanceof BitmapDrawable)
                    ((BitmapDrawable) ((ImageView) toClear).getDrawable()).getBitmap().recycle();
                v.removeView(toClear);
                toClear.destroyDrawingCache();
                if (((ImageView) toClear).getDrawable() != null) ((ImageView) toClear).getDrawable().setCallback(null);
                ((ImageView) toClear).setImageDrawable(null);
                ((ImageView) toClear).getResources().flushLayoutCache();
                ((ImageView) toClear).destroyDrawingCache();
                toClear = null;
            } else if (toClear instanceof ViewGroup) {
                clearView((ViewGroup) toClear);
            }
        }
        v.destroyDrawingCache();
    }

    /**
     * Allow a new delay before the automatic cache clear is done.
     */
    private void resetPurgeTimer() {
        purgeHandler.removeCallbacks(purger);
        purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
    }

    /**
     * Create a drawable from given bitmap for the given imageView.
     * If {@link ImageManager#displayWithMetrics} is true, so this funtion will use available ressources to scale the picture to the Activity density
     * 
     * @param bitmap
     * @param imageView
     * @return
     */
    private Drawable getScaledDrawable(Bitmap bitmap, ImageView imageView) {

        BitmapDrawable bmd = null;

        if (base_activity == null && displayWithMetrics && imageView != null) {
            try {

                base_activity = imageView.getContext();

            } catch (ClassCastException e) {
                //Log.w(LOG_TAG, "ClassCastException");
                bmd = new WeakReference<BitmapDrawable>(new BitmapDrawable(bitmap)).get();
            }
        } else if (!displayWithMetrics) {
            bmd = new WeakReference<BitmapDrawable>(new BitmapDrawable(bitmap)).get();
        } else {

            if (base_activity instanceof Activity) {
                DisplayMetrics metrics = new DisplayMetrics();
                ((Activity) base_activity).getWindowManager().getDefaultDisplay().getMetrics(metrics);
                Resources r = new Resources(base_activity.getAssets(), metrics, null);
                bmd = new WeakReference<BitmapDrawable>(new BitmapDrawable(r, bitmap)).get();
            } else {
                bmd = new WeakReference<BitmapDrawable>(new BitmapDrawable(bitmap)).get();
                //Log.w("ImageManager", "getScaledDrawable : base context is not an activity");
            }
        }

        return bmd;

    }

    public boolean isOnline(ImageView im) {
        ConnectivityManager cm = (ConnectivityManager) im.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        if (netInfo != null && netInfo.isConnectedOrConnecting()) {
            return true;
        }
        return false;
    }

    public void deleteCacheForUrl(String url) {
        if (mExternalStorageAvailable && url != null) {
            File folder = new File(mfolder);

            if (folder.exists()) {
                File[] files = folder.listFiles();
                for (File file : files) {
                    if (file.getName().startsWith(URLEncoder.encode(url))) {
                        file.delete();
                    }
                }
            }
        }

    }

}